package com.tuniu.agents.util;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Gson utility class providing common JSON processing functionalities
 */
public class JsonUtil {

    private static final Gson GSON = new Gson();
    private static final Gson URL_GSON = new GsonBuilder()
            .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
            .disableHtmlEscaping()
            .create();
    private static final Gson PRETTY_GSON = new GsonBuilder().setPrettyPrinting().create();
    private static final Gson NULL_GSON = new GsonBuilder().serializeNulls().create();

    /**
     * Private constructor to prevent instantiation
     */
    private JsonUtil() {
        throw new UnsupportedOperationException("GsonUtils cannot be instantiated");
    }

    /**
     * Get the default Gson instance
     *
     * @return Gson instance
     */
    public static Gson getGson() {
        return GSON;
    }

    /**
     * Get the Gson instance for formatted output
     *
     * @return Formatted Gson instance
     */
    public static Gson getPrettyGson() {
        return PRETTY_GSON;
    }

    /**
     * Get the Gson instance that serializes null values
     *
     * @return Gson instance that serializes null values
     */
    public static Gson getNullGson() {
        return NULL_GSON;
    }

    /**
     * Create a custom configured Gson instance
     *
     * @param builder GsonBuilder instance
     * @return Custom configured Gson instance
     */
    public static Gson createGson(GsonBuilder builder) {
        return builder.create();
    }

    /**
     * Convert an object to a JSON string
     *
     * @param obj The object to convert
     * @return JSON string
     */
    public static String toJson(Object obj) {
        return GSON.toJson(obj);
    }

    /**
     * Convert an object to a formatted JSON string
     *
     * @param obj The object to convert
     * @return Formatted JSON string
     */
    public static String toPrettyJson(Object obj) {
        return PRETTY_GSON.toJson(obj);
    }

    /**
     * Convert an object to a JSON string, including null values
     *
     * @param obj The object to convert
     * @return JSON string including null values
     */
    public static String toJsonWithNulls(Object obj) {
        return NULL_GSON.toJson(obj);
    }

    /**
     * Convert a JSON string to an object of the specified type
     *
     * @param json     JSON string
     * @param classOfT Target type
     * @param <T>      Generic type
     * @return Converted object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static <T> T fromJson(String json, Class<T> classOfT) {
        return GSON.fromJson(json, classOfT);
    }

    public static <T> T fromJsonWithUrl(String json, Class<T> classOfT) {
        return URL_GSON.fromJson(json, classOfT);
    }

    public static String toJsonWithUrl(Object src) {
        return URL_GSON.toJson(src);
    }

    /**
     * Convert a JSON string to an object of the specified type
     *
     * @param json    JSON string
     * @param typeOfT Target type
     * @param <T>     Generic type
     * @return Converted object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static <T> T fromJson(String json, Type typeOfT) {
        return GSON.fromJson(json, typeOfT);
    }

    public static <T> T fromJson(Object object, Type typeOfT) {
        String json = GSON.toJson(object);
        return GSON.fromJson(json, typeOfT);
    }

    /**
     * Convert a JSON string to a Map
     *
     * @param json JSON string
     * @return Map object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static Map<String, Object> toMap(String json) {
        Type type = new TypeToken<Map<String, Object>>() {
        }.getType();
        return GSON.fromJson(json, type);
    }

    /**
     * Convert a JSON string to a Map
     *
     * @param json JSON string
     * @return Map object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    // New generic method
    public static <T> Map<String, T> toMap(String json, Class<T> valueType) {
        Type type = TypeToken.getParameterized(Map.class, String.class, valueType).getType();
        return GSON.fromJson(json, type);
    }

    /**
     * Convert a JSON string to a List
     *
     * @param json     JSON string
     * @param classOfT List element type
     * @param <T>      Generic type
     * @return List object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static <T> List<T> toList(String json, Class<T> classOfT) {
        Type type = TypeToken.getParameterized(List.class, classOfT).getType();
        return GSON.fromJson(json, type);
    }

    /**
     * Convert an object to a JsonElement
     *
     * @param obj The object to convert
     * @return JsonElement object
     */
    public static JsonElement toJsonElement(Object obj) {
        return GSON.toJsonTree(obj);
    }

    /**
     * Convert a JSON string to a JsonElement
     *
     * @param json JSON string
     * @return JsonElement object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static JsonElement parseJson(String json) {
        return JsonParser.parseString(json);
    }

    /**
     * Get the value of a specified field from a JSON string
     *
     * @param json JSON string
     * @param key  Field name
     * @return Field value, returns null if the field does not exist
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static JsonElement getJsonField(String json, String key) {
        JsonElement element = JsonParser.parseString(json);
        if (element.isJsonObject()) {
            JsonObject jsonObject = element.getAsJsonObject();
            if (jsonObject.has(key)) {
                return jsonObject.get(key);
            }
        }
        return null;
    }

    /**
     * Get the value of a specified field from a JSON string and convert it to the specified type
     *
     * @param json     JSON string
     * @param key      Field name
     * @param classOfT Target type
     * @param <T>      Generic type
     * @return Converted object, returns null if the field does not exist
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static <T> T getJsonField(String json, String key, Class<T> classOfT) {
        JsonElement element = getJsonField(json, key);
        if (element != null) {
            return GSON.fromJson(element, classOfT);
        }
        return null;
    }

    /**
     * Merge two JSON objects
     *
     * @param json1 First JSON string
     * @param json2 Second JSON string
     * @return Merged JSON string
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static String mergeJson(String json1, String json2) {
        JsonElement element1 = JsonParser.parseString(json1);
        JsonElement element2 = JsonParser.parseString(json2);

        if (element1.isJsonObject() && element2.isJsonObject()) {
            JsonObject result = new JsonObject();
            JsonObject obj1 = element1.getAsJsonObject();
            JsonObject obj2 = element2.getAsJsonObject();

            // Copy all fields from the first object
            for (Map.Entry<String, JsonElement> entry : obj1.entrySet()) {
                result.add(entry.getKey(), entry.getValue());
            }

            // Add or overwrite fields from the second object
            for (Map.Entry<String, JsonElement> entry : obj2.entrySet()) {
                result.add(entry.getKey(), entry.getValue());
            }

            return GSON.toJson(result);
        }

        throw new JsonSyntaxException("Cannot merge non-object type JSON");
    }

    /**
     * Check if a string is a valid JSON object or JSON array
     *
     * @param jsonString Input string
     * @return Returns true if it is a valid JSON object or array, otherwise returns false
     */
    public static boolean isValidJson(String jsonString) {
        if (jsonString == null || jsonString.trim().isEmpty()) {
            return false; // Empty string or null is not valid JSON
        }

        try {
            // Try to parse the string as JsonElement
            JsonElement jsonElement = GSON.fromJson(jsonString, JsonElement.class);

            // Check if it is a JSON object or JSON array
            return jsonElement.isJsonObject() || jsonElement.isJsonArray();
        } catch (Exception e) {
            // Catch parsing exception, indicating it is not valid JSON
            return false;
        }
    }

    /**
     * Convert an object to a Base64 encoded JSON string
     *
     * @param obj The object to convert
     * @return Base64 encoded JSON string
     */
    public static String toBase64Json(Object obj) {
        String json = GSON.toJson(obj);
        return java.util.Base64.getEncoder().encodeToString(json.getBytes(java.nio.charset.StandardCharsets.UTF_8));
    }

    /**
     * Convert a Base64 encoded JSON string to an object of the specified type
     *
     * @param base64Json Base64 encoded JSON string
     * @param classOfT   Target type
     * @param <T>        Generic type
     * @return Converted object
     * @throws JsonSyntaxException If the JSON format is incorrect
     */
    public static <T> T fromBase64Json(String base64Json, Class<T> classOfT) {
        byte[] bytes = java.util.Base64.getDecoder().decode(base64Json);
        String json = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
        return GSON.fromJson(json, classOfT);
    }

    /**
     * Convert a JSON string to an object of the specified type, returning a default value if conversion fails
     *
     * @param json         JSON string
     * @param classOfT     Target type
     * @param defaultValue Default value
     * @param <T>          Generic type
     * @return Converted object, returns default value if conversion fails
     */
    public static <T> T fromJsonOrDefault(String json, Class<T> classOfT, T defaultValue) {
        try {
            return GSON.fromJson(json, classOfT);
        } catch (JsonSyntaxException e) {
            return defaultValue;
        }
    }

    /**
     * Convert an object to a JSON string, formatting and sorting it
     *
     * @param obj The object to convert
     * @return Formatted and sorted JSON string
     */
    public static String toSortedJson(Object obj) {
        Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .serializeNulls()
                .disableHtmlEscaping()
                .create();
        return gson.toJson(obj);
    }

    /**
     * Extract JSON content from a string
     *
     * @param json JSON string
     * @return Extracted JSON content
     */
    public static String getJsonString(String json) {
        String jsonContent = "";
        if (json == null || json.isEmpty()) {
            return jsonContent;
        }
        String regex = "\\s*(\\{[\\s\\S]*?\\})\\s*";
        Pattern pattern = Pattern.compile(regex);
        java.util.regex.Matcher matcher = pattern.matcher(json);

        if (matcher.find()) {
            jsonContent = matcher.group(1);
        }
        return jsonContent;
    }

    /**
     * Convert a JSON string to a JsonObject
     *
     * @param json JSON string
     * @return JsonObject object
     * @throws JsonSyntaxException If the JSON format is incorrect or not an object type
     */
    public static JsonObject toJsonObject(String json) {
        JsonElement element = JsonParser.parseString(json);
        if (element.isJsonObject()) {
            return element.getAsJsonObject();
        }
        throw new JsonSyntaxException("The provided JSON string is not a valid JsonObject");
    }

    /**
     * Get the value of a specified key from a JsonObject
     *
     * @param jsonObject JsonObject object
     * @param key        Key name
     * @return Value corresponding to the key, returns null if the key does not exist
     */
    public static JsonElement getJsonValue(JsonObject jsonObject, String key) {
        if (jsonObject.has(key)) {
            return jsonObject.get(key);
        }
        return null;
    }

    /**
     * Modify the value of a specified key in a JsonObject
     *
     * @param jsonObject JsonObject object
     * @param key        Key name
     * @param value      New value
     */
    public static void setJsonValue(JsonObject jsonObject, String key, JsonElement value) {
        jsonObject.add(key, value);
    }

    /**
     * Parse a JSON string into a list of objects of the specified type
     *
     * @param jsonData JSON data string
     * @param clazz    Class type of the target object
     * @param <T>      Generic parameter representing the target object type
     * @return Parsed list of objects
     */
    public static <T> List<T> parseJsonToList(String jsonData, Class<T> clazz) {
        Type listType = TypeToken.getParameterized(List.class, clazz).getType();
        return GSON.fromJson(jsonData, listType);
    }
}